﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
using DevExpress.XtraBars;
using DevExpress.XtraEditors;
using DevExpress.XtraTreeList.Nodes;
using Silent.MDB.Application.Core;
using Silent.MDB.Common.Data;
using Silent.MDB.Common.Services;

namespace Silent.MDB.Application
{
    public partial class MainWindow : XtraForm
    {
        #region Private Fields

        private const string RootFolderFilaName = "root_folders.xml";
        private static int m_objectId;
        private List<string> m_availableLocations;
        private List<Folder> m_rootFolders = new List<Folder>();
        private readonly Dictionary<int, RootObject> m_treeListDataSource = new Dictionary<int, RootObject>();
        private readonly Dictionary<Type, Action<RootObject, string>> m_addActions = new Dictionary<Type, Action<RootObject, string>>();
        private readonly Dictionary<Type, Action<RootObject, string>> m_updateActions = new Dictionary<Type, Action<RootObject, string>>();
        private readonly Dictionary<Type, Action<RootObject, string>> m_removeActions = new Dictionary<Type, Action<RootObject, string>>();

        #endregion

        public MainWindow()
        {
            InitializeComponent();
            InitializeControls();
            InitializeActions();
        }

        #region Properties

        private RootObject SelectedObject
        {
            get { return treeList.FocusedNode != null ? treeList.FocusedNode.Tag as RootObject : null; }
        }
        private Folder SelectedObjectFolder
        {
            get
            {
                TreeListNode currentNode = treeList.FocusedNode;
                while (currentNode != null)
                {
                    var currentItem = currentNode.Tag;
                    if (currentItem is Folder)
                        return currentItem as Folder;

                    currentNode = currentNode.ParentNode;
                }
                return null;
            }
        }
        private List<BarButtonItemLink> MenuItemLinks
        {
            get { return barItemAdd.ItemLinks.Cast<BarButtonItemLink>().Where(x => x.Caption != "Folder").ToList(); }
        }

        #endregion

        #region Initialization

        private void InitializeControls()
        {
            var menuItems = GetTypes().Select(TypeToMenuItem).Cast<BarItem>().ToArray();
            barItemAdd.ItemLinks.Clear();
            barItemAdd.ItemLinks.AddRange(menuItems);
        }
        private void InitializeData()
        {
            ApplicationCore.Initialize();
            var systemService = ApplicationCore.ClientServiceManager.GetService<ISystemInfoService>().Service;
            m_availableLocations = systemService.GetLocations();
            m_objectId = m_availableLocations.Select(systemService.GetLastId).Max();
        }
        private void InitializeActions()
        {
            InitializeActionForType<RootObject>();
            InitializeActionForType<Folder>();
            InitializeActionForType<Student>();
            InitializeActionForType<Teacher>();
            InitializeActionForType<Department>();
            InitializeActionForType<Faculty>();
            InitializeActionForType<Cathedra>();
        }
        private void InitializeActionForType<T>() where T : RootObject
        {
            m_addActions.Add(typeof(T), GetAddAction<T>());
            m_updateActions.Add(typeof(T), GetUpdateAction<T>());
            m_removeActions.Add(typeof(T), GetRemoveAction<T>());
        }
        private IEnumerable<Type> GetTypes()
        {
            return new List<Type>
                {
                    typeof (Folder),
                    typeof (Student),
                    typeof (Teacher),
                    typeof (Cathedra),
                    typeof (Faculty)
                };
        }
        private BarButtonItem TypeToMenuItem(Type type)
        {
            var button = new BarButtonItem { Caption = type.Name, Tag = type };
            button.ItemClick += BarButtonAddItemClick;
            return button;
        }
        private Action<RootObject, string> GetAddAction<T>() where T : RootObject
        {
            return (rootObject, location) => ApplicationCore.ClientServiceManager
                     .GetService<IRepositoryService<T>>()
                     .Service
                     .Add((T)rootObject, location);
        }
        private Action<RootObject, string> GetUpdateAction<T>() where T : RootObject
        {
            return (rootObject, location) => ApplicationCore.ClientServiceManager
                     .GetService<IRepositoryService<T>>()
                     .Service
                     .Update((T)rootObject, location);
        }
        private Action<RootObject, string> GetRemoveAction<T>() where T : RootObject
        {
            return (rootObject, location) => ApplicationCore.ClientServiceManager
                     .GetService<IRepositoryService<T>>()
                     .Service
                     .Remove((T)rootObject, location);
        }

        #endregion

        #region Data Management

        private void AddObject(RootObject rootObject, string location)
        {
            m_addActions[rootObject.GetType()].Invoke(rootObject, location);
        }
        private void UpdateObject(RootObject rootObject, string location)
        {
            m_updateActions[rootObject.GetType()].Invoke(rootObject, location);
        }
        private void RemoveObject(RootObject rootObject, string location)
        {
            if (m_treeListDataSource.ContainsKey(rootObject.ObjectId))
                m_treeListDataSource.Remove(rootObject.ObjectId);

            m_removeActions[rootObject.GetType()].Invoke(rootObject, location);
            treeList.Refresh();
        }
        private void RemoveObjectRecursive()
        {

        }
        private void LoadData<T>(T filter, string location) where T : RootObject
        {
            var objects = ApplicationCore.ClientServiceManager
                                         .GetService<IRepositoryService<T>>()
                                         .Service
                                         .Load(filter, location).ToList();

            var selectedNode = treeList.FocusedNode;
            treeList.BeginUnboundLoad();

            foreach (var item in objects)
            {
                RootObject rootObject = item;
                if (m_treeListDataSource.ContainsKey(item.ObjectId))
                {
                    rootObject = m_treeListDataSource[item.ObjectId];
                }
                else
                {
                    m_treeListDataSource.Add(rootObject.ObjectId, rootObject);
                }

                treeList.Invoke((Action)(() => treeList.AppendNode(new[] { rootObject.Name }, selectedNode, rootObject)));

                if (rootObject.ObjectId > m_objectId)
                {
                    m_objectId = rootObject.ObjectId + 1;
                }
            }

            treeList.EndUnboundLoad();
        }
        private void LoadCurrentNodeData()
        {
            var selectedObject = SelectedObject as Folder;
            int selectedObjectId;

            if (selectedObject != null)
            {
                selectedObjectId = 0;
            }
            else if (SelectedObject != null)
            {
                selectedObjectId = SelectedObject.ObjectId;
                selectedObject = SelectedObjectFolder;
                if (selectedObject == null)
                    return;
            }
            else
            {
                return;
            }

            //var types = GetTypes().Select(Activator.CreateInstance).Cast<RootObject>().ToList();

            LoadData(new Folder {ObjectId = selectedObjectId}, selectedObject.Location);
            LoadData(new Student {ObjectId = selectedObjectId}, selectedObject.Location);
            LoadData(new Teacher {ObjectId = selectedObjectId}, selectedObject.Location);
            LoadData(new Cathedra {ObjectId = selectedObjectId}, selectedObject.Location);
            LoadData(new Faculty {ObjectId = selectedObjectId}, selectedObject.Location);

            treeList.Invoke((Action)(() => treeList.Refresh()));
        }
        private void SaveData(string location)
        {
            ApplicationCore.ClientServiceManager
                .GetService<IRepositoryService<Folder>>()
                .Service
                .Save(DataCache.Instance.Folders, location);

            ApplicationCore.ClientServiceManager
                    .GetService<IRepositoryService<Student>>()
                    .Service
                    .Save(DataCache.Instance.Students, location);

            ApplicationCore.ClientServiceManager
                    .GetService<IRepositoryService<Teacher>>()
                    .Service
                    .Save(DataCache.Instance.Teachers, location);

            ApplicationCore.ClientServiceManager
                    .GetService<IRepositoryService<Department>>()
                    .Service
                    .Save(DataCache.Instance.Departments, location);

            ApplicationCore.ClientServiceManager
                    .GetService<IRepositoryService<Faculty>>()
                    .Service
                    .Save(DataCache.Instance.Faculties, location);

            ApplicationCore.ClientServiceManager
                    .GetService<IRepositoryService<Cathedra>>()
                    .Service
                    .Save(DataCache.Instance.Cathedras, location);
        }
        private void SaveAllData()
        {
            foreach (var location in m_availableLocations)
            {
                SaveData(location);
            }
        }

        #endregion

        #region Overrides

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            InitializeData();

            if (!File.Exists(RootFolderFilaName))
                return;

            using (var stream = File.OpenRead(RootFolderFilaName))
            {
                var serializer = new XmlSerializer(typeof(List<Folder>));
                m_rootFolders = (List<Folder>)serializer.Deserialize(stream);
            }

            foreach (var rootFolder in m_rootFolders)
            {
                treeList.AppendNode(new[] { rootFolder.Name }, null, rootFolder);
            }
        }
        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            SaveAllData();

            using (var stream = File.OpenWrite(RootFolderFilaName))
            {
                var serializer = new XmlSerializer(typeof (List<Folder>));
                serializer.Serialize(stream, m_rootFolders);
            }
        }

        #endregion

        #region Event Handlers

        private void AddWindowOnClosing(object sender, CancelEventArgs cancelEventArgs)
        {
            var window = sender as ObjectEditor;
            if (window == null)
                return;

            bool isParentObjectFolder = SelectedObject is Folder;
            bool isRootObject = treeList.FocusedNode == null;
            var parentFolder = isRootObject ? (Folder)window.SelectedObject : SelectedObjectFolder;

            window.Closing -= AddWindowOnClosing;

            if (window.DialogResult == DialogResult.OK)
            {
                window.SelectedObject.ObjectId = ++m_objectId;
                window.SelectedObject.ParentObjectId = isRootObject
                    ? window.SelectedObject.GetHashCode()
                    : (isParentObjectFolder ? 0 : SelectedObject.ObjectId);

                m_treeListDataSource.Add(window.SelectedObject.ObjectId, window.SelectedObject);
                if (!isRootObject)
                {
                    AddObject(window.SelectedObject, parentFolder.Location);
                }
                else
                {
                    m_rootFolders.Add((Folder)window.SelectedObject);
                }

                treeList.AppendNode(new[] { window.SelectedObject.Name }, treeList.FocusedNode, window.SelectedObject);
                treeList.FocusedNode.ExpandAll();
                treeList.Refresh();
            }
        }
        private void UpdateWindowOnClosing(object sender, CancelEventArgs cancelEventArgs)
        {
            var window = sender as ObjectEditor;
            if (window == null)
                return;

            if (!m_treeListDataSource.ContainsKey(SelectedObjectFolder.ParentObjectId))
                return;

            var selectedFolder = m_treeListDataSource[SelectedObject.ParentObjectId] as Folder;
            if (selectedFolder == null)
                return;

            window.Closing -= UpdateWindowOnClosing;

            if (window.DialogResult == DialogResult.OK)
            {
                UpdateObject(window.SelectedObject, selectedFolder.Location);
                treeList.Refresh();
            }
        }
        private void BarButtonAddItemClick(object sender, ItemClickEventArgs itemClickEventArgs)
        {
            var button = itemClickEventArgs.Item;
            if (button == null)
                return;

            var type = button.Tag as Type;
            if (type == null)
                return;

            var instance = Activator.CreateInstance(type) as RootObject;
            if (instance == null)
                return;

            var window = new ObjectEditor(instance);
            window.Closing += AddWindowOnClosing;
            window.Show(this);
        }
        private void BarButtonEditItemClick(object sender, ItemClickEventArgs itemClickEventArgs)
        {
            var window = new ObjectEditor(SelectedObject);
            window.Closing += UpdateWindowOnClosing;
            window.Show(this);
        }
        private void BarButtonRemoveItemClick(object sender, ItemClickEventArgs e)
        {
            if (!m_treeListDataSource.ContainsKey(SelectedObject.ParentObjectId))
                return;

            if (XtraMessageBox.Show(this, "Are you sure you want to remove?", "Remove",
                MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes)
            {
                RemoveObjectRecursive();
                //var selectedFolder = m_treeListDataSource[SelectedObject.ParentObjectId] as Folder;
                //if (selectedFolder != null)
                //{
                //    RemoveObject(SelectedObject, selectedFolder.Location);
                //}
            }
        }
        private void BarButtonSaveItemClick(object sender, ItemClickEventArgs e)
        {
            try
            {
                SaveAllData();

                XtraMessageBox.Show(this, "Saved successefully", "Success",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (XtraMessageBox.Show(this, "Failed to save data", "Fail",
                    MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
                {
                    SaveAllData();
                }
            }
        }
        private void barButtonExit_ItemClick(object sender, ItemClickEventArgs e)
        {
            System.Windows.Forms.Application.Exit();
        }
        private void barButtonAbout_ItemClick(object sender, ItemClickEventArgs e)
        {

        }
        private void barButtonLoad_ItemClick(object sender, ItemClickEventArgs e)
        {
            LoadCurrentNodeData();
        }
        private void treeList_MouseDown(object sender, MouseEventArgs e)
        {
            var hitInfo = treeList.CalcHitInfo(e.Location);
            treeList.FocusedNode = hitInfo.Node;
            treeList.Refresh();

            if (e.Button == MouseButtons.Right)
            {
                bool isRootObject = treeList.FocusedNode == null;
                MenuItemLinks.ForEach(x => x.Visible = !isRootObject);
                popupMenu.ShowPopup(treeList.PointToScreen(e.Location));
            }
        }

        #endregion
    }
}
